<script setup>
import { ref, computed } from 'vue'
import { ElButton, ElRow, ElProgress, ElMessage } from 'element-plus'

defineOptions({
	name: 'FileUpload'
})
const SIZE = 10 * 1024 * 1024 // 切片大小
const request = ({
	url,
	method = 'post',
	data,
	headers = {},
	requestList,
	onProgress = (e) => e
}) =>
	new Promise((resolve, reject) => {
		const xhr = new XMLHttpRequest()
		xhr.open(method, url)
		xhr.upload.onprogress = onProgress
		Object.keys(headers).forEach((header) => {
			xhr.setRequestHeader(header, headers[header])
		})
		xhr.send(data)
		xhr.onload = (e) => {
			//将请求成功的 xhr 从列表删除
			if (requestList) {
				const xhrIndex = requestList.findIndex((item) => item === xhr)
				requestList.splice(xhrIndex, 1)
			}
			resolve({ data: e.target.response })
		}
		requestList?.push(xhr)
	})

const container = ref({
	file: null,
	worker: null,
	hash: ''
})
const data = ref([])
const hashPercentage = ref(0)
const requestList = ref([])
const isPaused = ref(true)

const uploadPercentage = computed(() => {
	if (!container.value.file || data.value.length) return 0
	const loaded = data.value
		.map((item) => item.size * item.percentage)
		.reduce((acc, cur) => acc + cur, 0)
	return parseInt((loaded / container.value.file.size).toFixed(2))
})

const handleFileChange = (e) => {
	const [file] = e.target.files
	if (!file) return

	container.value.file = file
}

const handleUpload = async () => {
	if (!container.value.file) return

	const fileChunkList = createFileChunk(container.value.file, SIZE)

	container.value.hash = await calculateHash(fileChunkList)

	const { shouldUpload, uploadedList } = await verifyUpload(
		container.value.file.name,
		container.value.hash
	)

	if (!shouldUpload) {
		ElMessage.success('kip upload: file upload success')
		return
	}

	data.value = fileChunkList.map(({ file }, index) => ({
		fileHash: container.value.hash,
		chunk: file,
		hash: `${container.value.file.name}-${index}`,
		index,
		percentage: 0
	}))

	await uploadChunks(uploadedList)
}

const handlePause = () => {
	requestList.value.forEach((xhr) => xhr?.abort())
	requestList.value = []
	isPaused.value = false
}

const handleResume = async () => {
	const { uploadedList } = await verifyUpload(
		container.value.file.name,
		container.value.hash
	)
	await uploadChunks(uploadedList)
}

const uploadChunks = async (uploadedList = []) => {
	const requests = data.value
		.filter(({ hash }) => !uploadedList.includes(hash))
		.map(({ chunk, hash, fileHash, index }) => {
			const formData = new FormData()
			formData.append('chunk', chunk)
			formData.append('hash', hash)
			formData.append('fileHash', fileHash)
			formData.append('filename', container.value.file.name)

			return { formData, index }
		})
		.map(({ formData, index }) =>
			request({
				url: 'http://localhost:9001/upload',
				data: formData,
				onProgress: createProgressHandler(data.value[index]),
				requestList: requestList.value
			})
		)

	// 并发请求
	await Promise.all(requests)
	if (uploadedList.length + requests.length === data.value.length) {
		await mergeRequest()
	}
}

const createProgressHandler = (item) => {
	return (e) => {
		item.percentage = parseInt(String(e.loaded / e.total) * 100)
	}
}

const createFileChunk = (file, size = SIZE) => {
	const fileChunkList = []
	let cur = 0
	while (cur < file.size) {
		fileChunkList.push({ file: file.slice(cur, cur + size) })
		cur += size
	}

	return fileChunkList
}

const mergeRequest = async () => {
	await request({
		url: 'http://localhost:9001/merge',
		headers: {
			'content-type': 'application/json'
		},
		data: JSON.stringify({
			filename: container.value.file.name,
			size: container.value.file.size
		})
	})
}

const calculateHash = (fileChunkList) => {
	return new Promise((resolve) => {
		// 添加 worker 属性
		container.value.worker = new Worker('/hash.js')
		container.value.worker.postMessage({ fileChunkList })
		container.value.worker.onmessage = ({ data }) => {
			const { percentage, hash } = data
			hashPercentage.value = percentage
			if (hash) {
				resolve(hash)
			}
		}
	})
}

const verifyUpload = async (filename, fileHash) => {
	const { data } = await request({
		url: 'http://localhost:9001/verify',
		headers: {
			'content-type': 'application/json'
		},
		data: JSON.stringify({ filename, fileHash })
	})

	return JSON.parse(data)
}
</script>

<template>
	<input type="file" @change="handleFileChange" />
	<ElButton type="primary" @click="handleUpload">上传</ElButton>
	<ElButton type="primary" v-if="isPaused" @click="handlePause"
		>暂停上传</ElButton
	>
	<ElButton type="primary" v-else @click="handleResume">继续上传</ElButton>
	<ElRow>
		<ElProgress :percentage="uploadPercentage" style="width: 100%" />
	</ElRow>
</template>

<style scoped></style>
